﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.Globalization;

namespace Mandelbeep
{
    public partial class Main : Form
    {
        [DllImport("user32.dll")]
        static extern IntPtr GetDC(IntPtr hWnd);
        [DllImport("user32.dll")]
        static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
        [DllImport("gdi32.dll")]
        static extern int GetPixel(IntPtr hDC, int x, int y);
        [DllImport("gdi32.dll")]
        static extern int SetPixel(IntPtr hDC, int x, int y, int color);
        [DllImport("user32.dll")]
        public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); private const int WM_SETREDRAW = 11;


        IntPtr _hDC;
        Bitmap _imgbuffer;
        Bitmap _hiresimgbuffer;
        bool _playing = false;
        int _topcrop;
        int _bottomcrop;
        int _notescale;
        string _savefile = Application.StartupPath + "\\Mandelbeep.dat";
        bool _loading = false;
        bool _rendering = false;
        bool _rerender = false;

        private Midi midi = new Midi();

        public Main()
        {

            // this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB");
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");

            InitializeComponent();

            InitImgBuffers();
        }

        private void InitImgBuffers()
        {
            double bufferresolution = CalcBufferResolution();
            _hiresimgbuffer = new Bitmap((int)(img.Width * bufferresolution), (int)(img.Height * bufferresolution));
            _imgbuffer = new Bitmap(img.Width, img.Height);
        }

        private double CalcBufferResolution()
        {
            return (double.Parse(BufferResolution.Text));
        }

        private void Main_Load(object sender, EventArgs e)
        {
            Version v;

            if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
                v = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion;
            else
                v = new Version(Application.ProductVersion);

            Version.Text = "Version " + v.Major + "." + v.Minor + "." + v.Build + "." + v.Revision;

            LoadSavedSettingsFromFile();
            if (SavedItems.Items.Count > 0)
                LoadSettings(SavedItems.Items[0].ToString());

            RenderFractal();
        }

        private void LoadSavedSettingsFromFile()
        {
            if (File.Exists(_savefile))
            {
                StreamReader sr = new StreamReader(_savefile);
                SavedItems.Items.Clear();
                while (!sr.EndOfStream)
                    SavedItems.Items.Add(sr.ReadLine());
                sr.Close();
            }
        }

        private void DrawFractal()
        {
            Debug.WriteLine("      DrawFractal Start");
            _rerender = false;
            // user definable attributes
            double xmin = double.Parse(XMin.Text);
            double ymin = double.Parse(YMin.Text);
            double xmax = double.Parse(XMax.Text);
            double ymax = double.Parse(YMax.Text);
            int maxiterations = int.Parse(MaxIterations.Text);
            double scaler = double.Parse(MaxIterations.Text);
            int banding = int.Parse(Banding.Text);
            int colouringtype = int.Parse(ColouringType.Text);

            // split off into threads
            int cores = Environment.ProcessorCount * 4; // 2;
            int width = _hiresimgbuffer.Width;
            int height = _hiresimgbuffer.Height;
            int ysegment = height / cores;
            double range = 1.0 / cores;
            bool[] corerenderstatus = new bool[cores];

            String fractal = Fractal.Text;
            int variant = 0;
            int.TryParse(fractal.Substring(fractal.Length - 1, 1), out variant);
            int thingy = int.Parse(Thingy.Text);

            Graphics hg = Graphics.FromImage(_hiresimgbuffer);

            System.Object lockThis = new System.Object();

            for (int crs = 0; crs < cores; crs++)
                corerenderstatus[crs] = true;

            UpdateRenderStatus(corerenderstatus);

            List<Task> tasks = new List<Task>();

            Stopwatch sw = new Stopwatch();
            sw.Start();

            for (int i = 0; i < cores; i++)
            {
                int core = i;
                var t = Task.Factory.StartNew(() =>
                {
                    Bitmap result;

                    if (!_rerender)
                    {
                        result = DrawMandelbrotSection(core, xmin, ymin, xmax, ymax, range * core, width, height, core * ysegment, ysegment, maxiterations, scaler, banding, thingy, variant, colouringtype);

                        if (!_rerender)
                        {
                            lock (lockThis)
                            {
                                hg.DrawImage(result, 0, core * ysegment, width, ysegment);

                                corerenderstatus[core] = false;
                            }
                            this.Invoke(new Action(() =>
                            {
                                UpdateRenderStatus(corerenderstatus);
                                RedrawImage(true);
                            }));
                        }
                    }
                });
                tasks.Add(t);
            }

            Task.Factory.ContinueWhenAll(tasks.ToArray(),
                result =>
                {
                    sw.Stop();
                    _rendering = false;
                    if (_rerender)
                    {
                        this.Invoke(new Action(() => DrawFractal()));
                    }
                    else
                    {
                        this.Invoke(new Action(() => RedrawImage()));
                        this.Invoke(new Action(() => UpdateRenderStatus("Render took " + sw.ElapsedMilliseconds.ToString())));
                    }
                });

            if (_rerender)
                Debug.WriteLine("      DrawFractal exiting, rerender is flagged");
            else
                Debug.WriteLine("      DrawFractal exiting");
            //RedrawImage();
        }

        private void UpdateRenderStatus(string msg)
        {
            RenderStatus.Text = msg;
            RenderStatus.Refresh();
            // RedrawImage();
        }

        private void UpdateRenderStatus(bool[] corestatuses)
        {
            string rs;

            if (corestatuses == null)
                rs = "Idle...";
            else
            {
                rs = "Rendering segment ";
                for (int crs = 0; crs < corestatuses.Length; crs++)
                    rs += (corestatuses[crs] == true ? "X" : "O");
            }
            RenderStatus.Text = rs;
            RenderStatus.Refresh();
            // RedrawImage();
        }

        private void RedrawImage(Boolean quickdraw = false)
        {
            Panel p = new Panel();

            Graphics g = Graphics.FromImage(_imgbuffer);
            g.CompositingMode = CompositingMode.SourceCopy;
            if (quickdraw == true)
            {
                g.InterpolationMode = InterpolationMode.NearestNeighbor;
                g.SmoothingMode = SmoothingMode.None;
                g.CompositingQuality = CompositingQuality.HighSpeed;

            }
            else
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.SmoothingMode = SmoothingMode.HighQuality;
            }
            g.DrawImage(_hiresimgbuffer, 0, 0, _imgbuffer.Height, _imgbuffer.Width);
            img.Image = _imgbuffer;
            img.Refresh();
        }

        public Bitmap DrawMandelbrotSection(int core, double xmin, double ymin, double xmax, double ymax, double range, int width, int height, int drawstart, int drawheight, int maxiterations, double scaler, int banding, int thingy, int variant, int colouringtype)
        {
            Bitmap imgbuffer = new Bitmap(width, drawheight);

            double x, y, x1, y1, x2 = 0, y2 = 0, xx;
            double intigralX = (xmax - xmin) / width;
            double intigralY = (ymax - ymin) / height;
            x = xmin;

            int bandingwidth = 256 / banding;
            int c;

            int iteration, s, z = 0;
            double yrange = ymax - ymin;
            yrange *= range;

            for (s = 1; s < width; s++)
            {
                if (_rerender)
                {
                    Debug.WriteLine("...Thread exiting early due to re_render flag");
                    return (imgbuffer);
                }

                y = ymin + yrange;
                for (z = drawstart; z < (drawstart + drawheight); z++)
                {
                    x1 = 0;
                    y1 = 0;
                    iteration = 0;
                    switch (variant)
                    {
                        case 1:
                            while (iteration < maxiterations && (1 / (x1 + Math.Sqrt(y))) < thingy)
                            {
                                iteration++;
                                xx = (x1 * x1) - (y1 * y1) + x;
                                y1 = 2 * x1 * y1 + y;
                                x1 = xx;
                            }
                            break;
                        case 2:
                            while (iteration < maxiterations && (1 / (x1 + Math.Sqrt(x))) < thingy)
                            {
                                iteration++;
                                xx = (x1 * x1) - (y1 * y1) + x;
                                y1 = 2 * x1 * y1 + y;
                                x1 = xx;
                            }
                            break;
                        case 3:
                            while (iteration < maxiterations && ((x1 * y1) + (x1 * y1) < thingy))
                            {
                                iteration++;
                                xx = (x1 * x1) - (y1 * y1) + x;
                                y1 = 2 * x1 * y1 + y;
                                x1 = xx;
                            }
                            break;
                        case 4:
                            xx = (x1 * x1) + (y1 * y1);
                            while (iteration < maxiterations && (xx < thingy))
                            {
                                iteration++;
                                xx = (x1 * x1) - (y1 * y1) + x;
                                y1 = 2 * x1 * y1 + y;
                                x1 = xx;
                            }
                            break;
                        case 5:
                            x2 = x1 * x1;
                            y2 = y1 * y1;
                            while (iteration < maxiterations && (x2 + y2 < thingy))
                            {
                                iteration++;
                                // xx = x2 - y2 + x;
                                y1 = 2 * x1 * y1 + y;
                                // x1 = xx;
                                x1 = x2 - y2 + x;
                                x2 = x1 * x1;
                                y2 = y1 * y1;
                            }

                            break;
                        default:
                            // mandelbrot :)
                            while (iteration < maxiterations && ((x1 * x1) + (y1 * y1) < thingy))
                            {
                                iteration++;
                                xx = (x1 * x1) - (y1 * y1) + x;
                                y1 = 2 * x1 * y1 + y;
                                x1 = xx;
                            }
                            break;
                    }

                    x2 = x1 * x1;
                    y2 = y1 * y1;

                    switch (colouringtype)
                    {
                        case 2:
                            if (iteration < maxiterations)
                                c = (int)(iteration % bandingwidth) * banding;
                            else
                                c = 0;
                            break;
                        case 3:

                            if (iteration < maxiterations)
                            {
                                double i = (iteration - Math.Log(Math.Log(Math.Sqrt(x2 + y2)))) / maxiterations;
                                c = (int)(i * 255.0);
                                if (c < 0 || c > 255)
                                    c = 0;
                            }
                            else
                                c = 0;
                            break;
                        case 4:

                            if (iteration < maxiterations)
                            {
                                double i = iteration + 1.0 - (Math.Log(Math.Log(Math.Abs(x2 + y2))) / Math.Log(2.0));
                                c = (int)(i);
                                if (c < 0 || c > 255)
                                    c = 0;
                            }
                            else
                                c = 0;
                            break;
                        case 5:
                            if (iteration < maxiterations)
                            {
                                double i = iteration - (Math.Log(Math.Log(Math.Abs(x2 + y2))) / Math.Log(thingy));
                                c = (int)((i / maxiterations) * 255.0);
                                if (c < 0 || c > 255)
                                    c = 0;
                            }
                            else
                                c = 0;

                           // A better approach is to use sqrt scaling. Calculate the colval by colval=trunc(255*sqrt(hitcount)/sqrt(maxcount)). This leads to images that are not washed out in the high end of the hitcount and also not too pixelated in the low end.
                            break;
                        default:
                            c = (int)(iteration % bandingwidth) * banding;
                            break;
                    }

                    imgbuffer.SetPixel(s, z - drawstart, Color.FromArgb(c, c, c));
                    y += intigralY;
                }
                x += intigralX;
            }
            return imgbuffer;
        }

        private void PlayTune(string style)
        {
            int note;
            Color c;
            int xpos;
            int ypos;
            int notelength = int.Parse(NoteLength.Text);
            int maxnoterepeat = int.Parse(MaxNoteRepeat.Text);
            int currentnote = -1;
            int noterepeatcount = 1;
            Bitmap srcbuffer;
            double xscale;
            double yscale;
            if (PlayFromBuffer.Checked)
            {
                srcbuffer = _hiresimgbuffer;
                xscale = 1 / CalcBufferResolution();
                yscale = xscale;
            }
            else
            {
                srcbuffer = _imgbuffer;
                xscale = 1;
                yscale = 1;
            }
            Bitmap hudbuffer = _imgbuffer;

            _topcrop = int.Parse(TopCrop.Text);
            _bottomcrop = int.Parse(BottomCrop.Text);
            _notescale = int.Parse(NoteScale.Text);

            _playing = true;

            try
            {
                switch (style)
                {
                    case "Grid":
                        int xjump = int.Parse(XJump.Text);
                        int xstart = int.Parse(XStart.Text);
                        int yjump = int.Parse(YJump.Text);
                        int ystart = int.Parse(YStart.Text);

                        for (xpos = xstart; xpos < srcbuffer.Height; xpos += xjump)
                        {
                            for (ypos = ystart; ypos < srcbuffer.Width; ypos += yjump)
                            {
                                if (_playing == false)
                                    return;

                                c = srcbuffer.GetPixel(xpos, ypos);
                                note = c.R;
                                if (note > _bottomcrop && note < _topcrop)
                                {
                                    if (note != currentnote)
                                    {
                                        // new note - do it!
                                        DoNote(ypos, xpos, note, notelength);
                                        SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 255, 0), xscale, yscale, hudbuffer);
                                        currentnote = note;
                                        noterepeatcount = 1;
                                    }
                                    else if (currentnote == note && (maxnoterepeat == 0 || noterepeatcount < maxnoterepeat))
                                    {
                                        // repeat note, do it
                                        DoNote(ypos, xpos, note, notelength);
                                        SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 255, 0), xscale, yscale, hudbuffer);
                                        noterepeatcount++;
                                    }
                                    else
                                    {
                                        // too many repeats, skip it
                                        SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 255, 255), xscale, yscale, hudbuffer);
                                    }
                                }
                                else
                                    // outside our required range, skip it
                                    SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 0, 255), xscale, yscale, hudbuffer);

                                img.Image = hudbuffer;
                            }
                        }
                        break;
                    case "Circle":
                        int middlex = srcbuffer.Width / 2;
                        int middley = srcbuffer.Height / 2;
                        double rotationspeed = double.Parse(RotationSpeed.Text);
                        double radius = double.Parse(Radius.Text);
                        double radiuscreep = double.Parse(RadiusCreep.Text);
                        double totalangle = double.Parse(TotalAngle.Text);
                        double rad = Math.PI / 180.0;
                        double phaseoffset = double.Parse(PhaseOffset.Text);
                        double phasedrift = double.Parse(PhaseDrift.Text);
                        for (double angle = 0.0; angle < totalangle; angle += rotationspeed)
                        {
                            if (_playing == false)
                                return;

                            xpos = (int)(Math.Sin(angle * rad) * radius) + middlex;
                            ypos = (int)(Math.Cos((angle + phaseoffset) * rad) * radius) + middley;
                            phaseoffset += phasedrift;

                            c = srcbuffer.GetPixel(xpos, ypos);
                            note = c.R;

                            if (note > _bottomcrop && note < _topcrop)
                            {
                                if (note != currentnote)
                                {
                                    // new note - do it!
                                    DoNote(ypos, xpos, note, notelength);
                                    SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 255, 0), xscale, yscale, hudbuffer);
                                    currentnote = note;
                                    noterepeatcount = 0;
                                }
                                else if (currentnote == note && (maxnoterepeat == 0 || noterepeatcount < maxnoterepeat))
                                {
                                    // repeat note, do it
                                    DoNote(ypos, xpos, note, notelength);
                                    SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 255, 0), xscale, yscale, hudbuffer);
                                    noterepeatcount++;
                                }
                                else
                                {
                                    // too many repeats, skip it
                                    SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 255, 255), xscale, yscale, hudbuffer);
                                }
                            }
                            else
                                // outside our required range, skip it
                                SetBigPixel(xpos, ypos, Color.FromArgb(c.R, 0, 255), xscale, yscale, hudbuffer);

                            // update the display
                            img.Image = hudbuffer;

                            radius += radiuscreep;
                        }
                        break;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Tune stopped early (may have hit the edge). Actual Error: " + ex.Message, "Tune stopped", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }

        private void SetSinglePixel(int x, int y, Color c, Bitmap img)
        {
            if ((x >= 0 && x < img.Width) && (y >= 0 && y < img.Height))
                img.SetPixel(x, y, c);
        }

        private void SetBigPixel(int xpos, int ypos, Color c, double xscale, double yscale, Bitmap img)
        {
            xpos = (int)(xpos * xscale);
            ypos = (int)(ypos * yscale);

            SetSinglePixel(xpos, ypos, Color.FromArgb(c.R, 0, 0), img);
            SetSinglePixel(xpos + 1, ypos, c, img);
            SetSinglePixel(xpos - 1, ypos, c, img);
            SetSinglePixel(xpos, ypos + 1, c, img);
            SetSinglePixel(xpos, ypos - 1, c, img);
        }

        private int _previousnote = 0;
        private int _previousvelocity = 0;

        private void DoNote(int x, int y, int note, int notelength, int channel = 0)
        {
            // note should be 0 - 255
            // valid midi notes are 0-127 (middle C is 60)

            //int r;
            // Turn off existing note
            //r = midiOutShortMsg(_midihandler, _midinoteoff | _midichannel | _previousnote << 8 | _previousvelocity << 16);
            // Instrument
            // midiOutShortMsg(_midihandler, $C0 |  channel | instrument << 8 ); Default=piano 0
            // scale
            note = note / 2; // 0->225 -> 0->128
            note /= _notescale; // scale down if required
            note += (128 - (128 / _notescale)) / 2; // recentralise some what
            _previousnote = note;
            _previousvelocity = 100;
            //r = midiOutShortMsg(_midihandler, _midinoteon | _midichannel | note << 8 | _midivelocity << 16);

            midi.PlayNote(note, notelength, 127, channel);

            TestLabel.Text = "Note:" + note; // +", NRange:" + (note * range).ToString() + ", Freq. " + frequency;
            Application.DoEvents();
            System.Threading.Thread.Sleep(notelength);
        }

        private int CalcFrequency(int note)
        {
            //return 440 * 2 ^ ((note - 49) / 12);
            return 440 * ((2 ^ (1 / 12)) ^ note);
        }

        private void Draw_Click(object sender, EventArgs e)
        {
            RenderFractal();
        }

        private void RenderFractal()
        {
            Debug.WriteLine("Render Fractal...");
            if (_loading == true)
            {
                Debug.WriteLine("   Early exit due to load");
                return;
            }

            if (_rendering == true)
            {
                // cancel current render threads (and don't re-trigger rendering till these have finished)
                Debug.WriteLine("   Early exit due to already rendering, flagged for restart");
                _rerender = true;
                return;
            }

            Debug.WriteLine("   Render Start (rerender=" + _rerender.ToString() + ")");
            _rendering = true;
            _rerender = false;
            _hDC = GetDC(img.Handle);
            DrawFractal();
            ReleaseDC(img.Handle, _hDC);
            Debug.WriteLine("   Render End (rerender=" + _rerender.ToString() + ")");

        }

        private void SpankThatTune(string type)
        {
            if (_playing == true)
                _playing = false;
            else
            {
                RedrawImage();
                _hDC = GetDC(img.Handle);
                PlayTune(type);
                ReleaseDC(img.Handle, _hDC);
                _playing = false;
                // int r = midiOutShortMsg(_midihandler, _midictrlchg | _midichannel | _midiallsoundoff);
            }
        }

        private void PlayGrid_Click(object sender, EventArgs e)
        {
            SpankThatTune("Grid");
        }

        private void PlayCircle_Click(object sender, EventArgs e)
        {
            SpankThatTune("Circle");
        }

        private void img_MouseClick(object sender, MouseEventArgs e)
        {
            Boolean somethingdone = false;

            if (LockX.Checked == false)
            {
                double x = e.X;
                double xmin = double.Parse(XMin.Text);
                double xmax = double.Parse(XMax.Text);

                double xinfluence = ((img.Width - x) / img.Width);  // gives us 0.0 -> 1.0 with 0.5 being in the middle. 
                xinfluence = 1.0 - (xinfluence * 2);    // gives us 1.0 -> -1.0 with 0 being in the middle
                double xoffset = ((xmax - xmin) / 2) * xinfluence;
                xmin += xoffset;
                xmax += xoffset;

                XMin.Text = xmin.ToString();
                XMax.Text = xmax.ToString();

                somethingdone = true;
            }

            if (LockY.Checked == false)
            {
                double y = e.Y;
                double ymin = double.Parse(YMin.Text);
                double ymax = double.Parse(YMax.Text);

                double yinfluence = ((img.Height - y) / img.Height);
                yinfluence = 1.0 - (yinfluence * 2);
                double yoffset = ((ymax - ymin) / 2) * yinfluence;
                ymin += yoffset;
                ymax += yoffset;

                YMin.Text = ymin.ToString();
                YMax.Text = ymax.ToString();

                somethingdone = true;
            }

            if (Autozoom.Checked == true)
            {
                AlterZoom(0.5);
                somethingdone = true;
            }

            if (somethingdone)
                RenderFractal();
        }

        private void Zoom_Click(object sender, EventArgs e)
        {
            AlterZoom(0.5);
        }

        private void AlterZoom(double zoom)
        {
            double xmin = double.Parse(XMin.Text);
            double ymin = double.Parse(YMin.Text);
            double xmax = double.Parse(XMax.Text);
            double ymax = double.Parse(YMax.Text);

            double xdiff = (xmax - xmin);
            xdiff = (xdiff - (xdiff * zoom)) / 2;
            double ydiff = (ymax - ymin);
            ydiff = (ydiff - (ydiff * zoom)) / 2;
            xmin += xdiff;
            xmax -= xdiff;
            ymin += ydiff;
            ymax -= ydiff;

            XMin.Text = xmin.ToString();
            YMin.Text = ymin.ToString();

            XMax.Text = xmax.ToString();
            YMax.Text = ymax.ToString();
        }

        void SaveSettings()
        {
            StringBuilder sb = new StringBuilder(SetDescription.Text + ";");
            foreach (Control ctrl in this.Controls)
            {
                if (ctrl.Tag != null && ctrl.Tag.ToString().Contains("S"))
                {
                    if (ctrl is CheckBox)
                        sb.Append(ctrl.Name + ":" + ((CheckBox)ctrl).Checked.ToString() + ";");
                    else
                        sb.Append(ctrl.Name + ":" + ctrl.Text + ";");
                }
            }

            StreamWriter sw = new StreamWriter(_savefile, true);
            sw.WriteLine(sb.ToString());
            sw.Close();

            LoadSavedSettingsFromFile();
        }

        void LoadSettings(string config)
        {
            _loading = true;
            string[] vals = config.Split(';');
            SetDescription.Text = vals[0];
            for (int loopv = 1; loopv < vals.GetUpperBound(0); loopv++)
            {
                string[] components = vals[loopv].Split(':');
                Control ctrl = this.Controls[components[0]];
                if (ctrl is CheckBox)
                    ((CheckBox)ctrl).Checked = bool.Parse(components[1]);
                else
                    ctrl.Text = components[1];
            }
            _loading = false;
        }

        private void Save_Click(object sender, EventArgs e)
        {
            SaveSettings();
        }

        private void SavedItems_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (MessageBox.Show("Load these settings?", "Load these settings?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
            {
                LoadSettings(SavedItems.SelectedText);
                RenderFractal();
            }
        }

        private void StopPlay_Click(object sender, EventArgs e)
        {
            _playing = false;
        }

        private void Main_FormClosing(object sender, FormClosingEventArgs e)
        {
            _playing = false;
        }

        private void ZoomOut_Click(object sender, EventArgs e)
        {
            AlterZoom(2);
        }

        private void LoadImage_Click(object sender, EventArgs e)
        {
            var FD = new System.Windows.Forms.OpenFileDialog();
            if (FD.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                Bitmap tmp = new Bitmap(FD.FileName);

                Graphics g = Graphics.FromImage(_hiresimgbuffer);
                g.DrawImage(tmp, 0, 0, _hiresimgbuffer.Width, _hiresimgbuffer.Height);

                RedrawImage();
                // img.Image = _imgbuffer;
            }

        }

        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            midi.Cleanup();
        }

        private void Banding_SelectedIndexChanged(object sender, EventArgs e)
        {
            RenderFractal();
        }

        private void BufferResolution_SelectedIndexChanged(object sender, EventArgs e)
        {
            InitImgBuffers();
            RenderFractal();
        }

        private void RefreshFromBuffer_Click(object sender, EventArgs e)
        {
            RedrawImage();
        }

        private void ResetFractal_Click(object sender, EventArgs e)
        {
            XMin.Text = "-2.1";
            YMin.Text = "-1.3";
            XMax.Text = "1";
            YMax.Text = "1.3";

            RenderFractal();
        }

        private void MaxIterations_SelectedIndexChanged(object sender, EventArgs e)
        {
            RenderFractal();
        }

        private void Fractal_SelectedIndexChanged(object sender, EventArgs e)
        {
            RenderFractal();
        }

        private void Thingy_SelectedIndexChanged(object sender, EventArgs e)
        {
            RenderFractal();
        }

        private void ColouringType_SelectedIndexChanged(object sender, EventArgs e)
        {
            RenderFractal();
        }
    }
}
